package core.algorithms;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import core.interfaces.IBooleanOperator;
import core.interfaces.IProgressListener;
import core.interfaces.ISearchAlgorithm;
import core.interfaces.IState;
import core.interfaces.IUtility;

public abstract class HillClimbing<T>  implements ISearchAlgorithm<T>
{
	//Members
//	protected int 							m_MaximumSteps = 0;

/**
	 * 
	 */
	private static final long serialVersionUID = -5267678589039282927L;
	//	protected double 						m_FinalResult = -1;
//	protected IState<T>						m_FinalState = null;
	protected SearchParameters<T> 			m_SearchParameters = null;
	protected SearchResults<T>				m_SearchResults = null;
	protected Random 						m_Randomizer = new Random();
	protected List<IProgressListener> 		m_IProgressListenerList = new ArrayList<IProgressListener>();
	protected int 							m_StepsTaken = 0;
	protected int							m_HeuristicCallsTaken = 0;
	
	private	  boolean 						m_IsFinished;
	private   IUtility<IState<T>, Double> 	m_Heuristic = null;
	
	@Override
	public HillClimbing<T> clone()
	{
		return commonUtils.SerialClone.clone(this);
	}
	
	@Override
	public List<IProgressListener> getProgressListeners()
	{
		return m_IProgressListenerList;
	}
	@Override
	public void setProgressListeners(List<IProgressListener> listeners)
	{
		m_IProgressListenerList = listeners;
	}
	@Override
	public void initAlgorithm(SearchParameters<T> searchParameters)
	{
		Date date = new Date();
		m_SearchParameters = searchParameters;
		m_SearchResults = null;
		m_Randomizer = new Random(date.getTime());
		m_StepsTaken = 0;
		m_HeuristicCallsTaken = 0;
		
		m_IsFinished = false;
		m_Heuristic = searchParameters.getHeuristic();
	}
	
	//protected methods
	protected HillClimbing(SearchParameters<T> searchParameters)
	{
		initAlgorithm(searchParameters);
	}
	
	protected boolean isDoAnotherIteration(boolean checkHeuristicCalls)
	{
		if (checkHeuristicCalls)
		{
			return m_SearchParameters.getMaximumHeuristicCalls() > m_HeuristicCallsTaken;
		}
		else
		{
			return m_SearchParameters.getMaximumHeuristicCalls() > m_StepsTaken;
		}
	}
	
	protected double callHeuristicFunction(IState<T> state)
	{
		m_HeuristicCallsTaken++;
		return m_Heuristic.getValue(state);
	}
	
	protected double findBestOperator(IState<T> currentState, double bestVal,
			List<IBooleanOperator> bestOperators,
			List<? extends IBooleanOperator> possibleOperators)
	{
		for (IBooleanOperator operator : possibleOperators)
		{
			// try the current operator
			currentState.doOperator(operator);
			
			// get the value for the new node
			// m_MaximumHeuristicCalls--;
			double neighbourValue = callHeuristicFunction(currentState);
			
			// check current results
			if (neighbourValue > bestVal) 
			{
				bestOperators.clear();
				bestOperators.add(operator);
				bestVal = neighbourValue;
			}
			else if (neighbourValue == bestVal)
			{
				bestOperators.add(operator);
			}
			
			// undo the operator
			currentState.doOperator(operator);
		}
		return bestVal;
	}
	
	protected void stepFinished()
	{
		m_StepsTaken++;
		notifyProgressListners(m_StepsTaken);
	}
	
	private double getPercentagComplete(int stepsMade)
	{
		return (stepsMade * 100.0) / (double)m_SearchParameters.getMaximumHeuristicCalls();
	}
	
	@Override
	public void addProgressListener(IProgressListener progressListener)
	{
		m_IProgressListenerList.add(progressListener);
	}
	
	private void notifyProgressListners(int stepsMade)
	{
		if (stepsMade % 30 == 0)
		{
			double percntageComplete = getPercentagComplete(stepsMade);
			for (IProgressListener progressListener : m_IProgressListenerList)
			{
				progressListener.percentageComplete(percntageComplete, 2);
			}
		}
	}
	
	protected IState<T> getRandom(List<IBooleanOperator> bestOperators, IState<T> state)
	{
		int nextIndex = m_Randomizer.nextInt(bestOperators.size());
		state.doOperator(bestOperators.get(nextIndex));
		return state; 
		
	}
	
	protected void saveBestState(IState<T> currentState,
			double currentHeuristicValue)
	{
		if(getSearchResults().getFinalStateValue() < currentHeuristicValue)
		{
			IState<T> clonedState = currentState.clone();
			getSearchResults().setFinalStateValue(currentHeuristicValue);
			getSearchResults().setFinalState(clonedState);
//			m_FinalResult  = currentHeuristicValue;
//			m_FinalState = currentState;
		}
	}
	
	// protected abstract IState<T> start(IState<T> initialState);
	
	//public Methods:
//	@Override
//	public double getFinalResult()
//	{
//		return m_FinalResult;
//	}

//	@Override
//	public IState<T> getFinalState()
//	{
//		return m_FinalState;
//	}

	@Override
	public SearchParameters<T> getSearchParameters()
	{
		return m_SearchParameters;
	}

	@Override
	public boolean isFinished()
	{
		return m_IsFinished;
	}
	
	@Override
	public core.algorithms.SearchResults<T> getSearchResults()
	{
		return m_SearchResults;
	};
	
	@Override
	public SearchResults<T> start()
	{
		m_SearchResults = doAlgorithm(m_SearchParameters.getInitialState());
		setIsFinished(true);
		return m_SearchResults;
	}
	
	protected abstract SearchResults<T> doAlgorithm(IState<T> initialState);
		

	protected void setIsFinished(boolean isFinished)
	{
		this.m_IsFinished = isFinished;
	}
	

}
